#ifndef YCBASELOGGER_H
#define YCBASELOGGER_H
#include <functional>
#include <QDebug>
#include <QMutex>
#include <QObject>
#include <QPointer>

QT_BEGIN_NAMESPACE
class QString;
class QFile;
QT_END_NAMESPACE

namespace yc {

enum LogLevel {
    DEBUG_LOG_LEVEL = 0,
    INFO_LOG_LEVEL = 1,
    WARNING_LOG_LEVEL,
    CRITICAL_LOG_LEVEL,
    FATAL_LOG_LEVEL,
    ERROR_LOG_LEVEL,
};

class Logger
{
public:
    explicit Logger(const QString& file_name, int cache_size = 0);
    ~Logger();

    Logger& operator<<(const QString& text);

    static QString getLogFileName(const QString& base_name);
    static void clearExpiredLogFiles(const QString& dir_path, int days = 5);

private:
    const int cache_size_;
    QFile* file_;
    QString cached_text_;
    QMutex mutex_;
};

template<class T>
struct LogFunctionTrait
{
    using F = void (T::*)(int, const QString&);
};

class LogHandler
{
public:
    using F = std::function<void(int, const QString&)>;

    LogHandler() { }

    template<class T>
    LogHandler(T* obj, typename LogFunctionTrait<T>::F f)
        : is_qo_f_(true), obj_(obj), obj_used_(obj != nullptr)
    {
        f_ = std::bind(f, obj, std::placeholders::_1, std::placeholders::_2);
    }
    template<class T>
    explicit LogHandler(T* obj, const F& f)
        : is_qo_f_(false), obj_(obj), obj_used_(obj != nullptr), f_(f)
    {
    }
    explicit LogHandler(const F& f) : is_qo_f_(false), obj_used_(false), f_(f) { }

    LogHandler(const LogHandler& other)
        : is_qo_f_(other.is_qo_f_), obj_(other.obj_), obj_used_(other.obj_used_), f_(other.f_)
    {
    }

    operator bool() const
    {
        if (obj_used_ && obj_.isNull())
            return false;
        return (!is_qo_f_ && f_) || (is_qo_f_ && obj_);
    }
    void operator()(int level, const QString& message) const
    {
        if (obj_used_ && obj_.isNull()) {
            return;
        }
        if ((!is_qo_f_ && f_) || (is_qo_f_ && obj_)) {
            f_(level, message);
        }
    }

private:
    bool is_qo_f_;
    QPointer<QObject> obj_;
    bool obj_used_;
    F f_;
};

class LogStream
{
public:
    explicit LogStream(QMutex* mutex, int level, LogHandler handler)
        : mutex_(mutex), level_(level), handler_(handler)
    {
        if (mutex) {
            mutex->lock();
        }
    }

    ~LogStream()
    {
        handler_(level_, ba_);
        if (mutex_) {
            mutex_->unlock();
        }
    }

    QDebug qdebug() { return QDebug(&ba_); }

private:
    QMutex* mutex_;
    int level_;
    LogHandler handler_;
    QString ba_;
};

void installLogHandler(LogHandler handler);
LogStream logStream(int level);

} // namespace yc

#define logDebug() yc::logStream(yc::DEBUG_LOG_LEVEL).qdebug()
#define logInfo() yc::logStream(yc::INFO_LOG_LEVEL).qdebug()
#define logWarning() yc::logStream(yc::WARNING_LOG_LEVEL).qdebug()
#define logError() yc::logStream(yc::ERROR_LOG_LEVEL).qdebug()
#define logCritical() yc::logStream(yc::CRITICAL_LOG_LEVEL).qdebug()
#define logFatal() yc::logStream(yc::FATAL_LOG_LEVEL).qdebug()

#endif // YCBASELOGGER_H
